1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.util.reflection;
12 int asInt(alias enumMember)()
13 {
14     alias T = typeof(enumMember);
15     foreach(i, mem; __traits(allMembers, T))
16     {
17         if(mem == enumMember.stringof)
18             return i;
19     }
20     ErrorHandler.assertLazyExit(0, "Member "~enumMember.stringof~" from type " ~ T.stringof~ " does not exist");
21 }
22 
23 /**
24 *   Used when wanting to represent any struct compatible with a static array.
25 */
26 bool isTypeArrayOf(Type, Array, int Count)()
27 {
28     static if(is(Array == Type[Count]))
29         return true;
30     else static if(is(Array T : T*))
31         return false;
32     else
33     {
34         int count = 0;
35   		static foreach(v; Array.tupleof)
36     		static if(is(typeof(v) == Type))
37       			count++;
38             else static if(__traits(isStaticArray, typeof(v)) && is(typeof(v.init[0]) == Type))
39             	count+= v.length;
40   		return count == Count;
41     }
42 }
43 
44 bool isLiteral(alias variable)(string var = variable.stringof)
45 {
46     import hip.util.string : isNumeric;
47     return (isNumeric(var) || (var[0] == '"' && var[$-1] == '"'));
48 }
49 
50 
51 ///Copy pasted from std.traits for not importing too many things
52 template isFunction(X...)
53 if (X.length == 1)
54 {
55     static if (is(typeof(&X[0]) U : U*) && is(U == function) ||
56                is(typeof(&X[0]) U == delegate))
57     {
58         // x is a (nested) function symbol.
59         enum isFunction = true;
60     }
61     else static if (is(X[0] T))
62     {
63         // x is a type.  Take the type of it and examine.
64         enum isFunction = is(T == function);
65     }
66     else
67         enum isFunction = false;
68 }
69 
70 template getParams (alias fn) 
71 {
72 	static if ( is(typeof(fn) params == __parameters) )
73     	alias getParams = params;
74 }
75 alias Parameters = getParams;
76 enum hasModule(string modulePath) = (is(typeof((){mixin("import ", modulePath, ";");})));
77 enum hasType(string TypeName) = __traits(compiles, {mixin(TypeName, " _;");});
78 
79 template isEnum(alias s)
80 {
81     static if(is(s == enum))
82         enum bool isEnum = true;
83     else
84         enum bool isEnum = false;
85 }
86 
87 
88 template getUDAs(alias symbol)
89 {
90     enum getUDAs = __traits(getAttributes, symbol);
91 }
92 
93 template hasUDA(alias symbol, alias UDA)
94 {
95     enum helper = ()
96     {
97         bool ret = false;
98         foreach(att; __traits(getAttributes, symbol))
99         {
100             static if(is(typeof(UDA)))
101             {
102                 if(att == UDA) ret = true;
103             }
104             else
105             {
106                 if(is(typeof(att) == UDA) || is(att == UDA)) ret = true;
107             } 
108         }
109         return ret;
110     }();
111     enum hasUDA = helper;
112 }
113 template isReference(T)
114 {
115     enum isReference = is(T == class) || is(T == interface);
116 }
117 template hasMethod(T, string method, Params...)
118 {
119     enum hasMethod = __traits(hasMember, T, method) && is(getParams!(__traits(getMember, T, method)) == Params);
120 }
121 
122 auto inverseLookup(alias lookupTable)()
123 {
124     alias O = typeof(lookupTable.keys[0]);
125     alias I = typeof(lookupTable.values[0]);
126     O[I] output;
127     static foreach(k, v; lookupTable)
128         output[v] = k;
129     return output;
130 }
131 
132 /** 
133 *   Generates a function that executes a switch case from the associative array.
134 *
135 *   Pass true as the second parameter if reverse is desired.
136 */
137 template aaToSwitch(alias aa, bool reverse = false)
138 {
139     auto aaToSwitch(T)(T value)
140     {
141         switch(value)
142         {
143             static foreach(k, v; aa)
144                 static if(reverse)
145                     case v: return k;
146                 else
147                     case k: return v;                
148             default:
149                 return typeof(return).init;
150         }
151     }
152 }
153 
154 
155 size_t enumLength(T)()
156 if(is(T == enum))
157 {
158     return __traits(allMembers, T).length;
159 }
160 
161 T nullCheck(string expression, T, Q)(T defaultValue, Q target)
162 {
163     import std.traits:ReturnType;
164     import hip.util.conv:to;
165     import hip.util.string:split;
166 
167     enum exps = expression.split(".");
168     enum firstExp = exps[0]~"."~exps[1];
169 
170     mixin("alias ",exps[0]," = target;");
171     
172     if(target is null)
173         return defaultValue;
174     
175     static if(mixin("isFunction!("~firstExp~")"))
176     {
177         mixin("ReturnType!("~firstExp~") _v0 = "~firstExp~";");
178         if(_v0 is null) return defaultValue;
179     }
180     else
181         mixin("typeof("~firstExp~") _v0 = " ~ firstExp~";");
182     
183     static foreach(i, e; exps[2..$])
184     {
185         static if(mixin("isFunction!(_v"~to!string(i)~"."~e~")"))
186         {
187             mixin("ReturnType!(_v"~to!string(i)~"."~e~") _v"~to!string(i+1) ~"= _v"~to!string(i)~"."~e~";");
188         }
189         else
190         {
191             mixin("typeof(_v"~to!string(i)~"."~e~") _v"~to!string(i+1)~"= _v"~to!string(i)~"."~e~";");
192         }
193         static if(i != exps[2..$].length - 1)
194         	mixin("if (_v"~to!string(i+1)~" is null) return defaultValue;");
195     }
196 
197     mixin("return _v",to!string(exps.length-2),";");
198 }
199 
200 
201 /**
202 * Used on: 
203 *   - Static Methods
204 *   - Class Names
205 *   Used in conjunction to ExportDFunctions.
206 *   You may specify a suffix, if you so, `_suffix` is added
207 *   ExportD will do nothing to static methods when building release. However, it will still produce
208 *   a function for returning a new class.
209 */
210 struct ExportD{string suffix;}
211 
212 
213 /**
214 *   Will basically generate an export name such as className_funcSymbol
215 *   If it has ExportD with a suffix, it will be basically className_funcSymbol(suffix)
216 */
217 template generateExportName(string className, alias funcSymbol)
218 {
219 	//Means that it has a suffix
220 	static if(is(typeof(__traits(getAttributes, funcSymbol)[0]) == ExportD))
221 		enum generateExportName = className~"_"~__traits(identifier, funcSymbol)~"_"~__traits(getAttributes, funcSymbol)[0].suffix;
222 	else
223 		enum generateExportName = className~"_"~__traits(identifier, funcSymbol);
224 }
225 
226 /**
227 *   Returns a code to be mixed in.
228 *   If isRef, it will call with hipSaveRef for not being colled
229 *   funcCallCode can be anything as `className.functionName` or even `new Class`
230 */
231 private string getExportedFuncImpl(bool isRef, string funcCallCode)
232 {
233     string ret;
234     if(isRef)
235     {
236         ret = q{
237             import hip.util.lifetime;
238             return cast(typeof(return))hipSaveRef(cast(Object)
239         } ~ funcCallCode~"(__traits(parameters)));}";
240     }
241     else
242     {
243         ret = "return "~funcCallCode~"(__traits(parameters));}";
244     }
245     return ret;
246 }
247 
248 
249 ///ClassT, Ctor, string className
250 ///This class MUST have an interface, because it will bug out when calling the function with `need opCmp for class`
251 template generateExportConstructor(ClassT, string className)
252 {
253     enum impl = ()
254     {
255         return "export extern(System) I" ~ className ~ " new" ~ className ~ //export extern(System) Class newClass
256         "(getParams!(__traits(getMember, Class, \"__ctor\"))){"~ //(A a, B b...)
257         getExportedFuncImpl
258         (
259             true, //IsReference
260             "new "~className
261         );
262     }();
263 
264     enum generateExportConstructor = impl;
265 }
266 
267 /**
268 *   It will create a `export extern(System)` function, thus, making it a C callable code.
269 *   This function comes from a static method, and has special code injection for making the
270 *   GC not collect if it is an object
271 */
272 template generateExportFunc(string className, alias funcSymbol)
273 {
274     import std.traits:ReturnType;
275     enum impl = ()
276     {
277         alias RetType = ReturnType!funcSymbol;
278         string ret = "export extern(System) "~RetType.stringof~" "~generateExportName!(className, funcSymbol);
279         ret~= "(getParams!(sym)){";
280 
281         ret~= getExportedFuncImpl
282         (
283             isReference!(RetType),
284             className~"."~__traits(identifier, funcSymbol)
285         );
286 
287         return ret;
288     }();
289 
290     enum generateExportFunc = impl;
291 }
292 
293 
294 struct Version
295 {
296     template opDispatch(string s)
297     {
298         mixin(`version(`~s~`)enum opDispatch=true;else enum opDispatch=false;`);
299     }
300 }
301 
302 
303 
304 ///Intermediary step for getting an alias to the Class type
305 mixin template ExportDFunctionsImpl(string className, Class)
306 {
307     //If the class has ExportD, it will export a function called new(ClassName)
308     //It can't contain more than one constructor.
309     static if(hasUDA!(Class, ExportD))
310     {
311         static assert(
312             __traits(getOverloads, Class, "__ctor").length == 1, 
313             "Can't export class with more than one constructor ("~className~")"
314         );
315         mixin(
316             generateExportConstructor!(Class, className)
317         );
318         pragma(msg, "Exported Class "~className);
319     }
320     //Get all static methods that has ExportD
321     static foreach(sym; getSymbolsByUDA!(Class, ExportD))
322     {
323         static if(!is(sym == Class))
324         {
325             //Assert that the symbol to generate does not exists yet
326             static assert(__traits(compiles, mixin(generateExportName!(className, sym))),
327             "ExportD '" ~ generateExportName!(className, sym) ~
328             "' is not unique, use ExportD(\"SomeName\") for overloading with a suffix");
329 
330             pragma(msg, "Exported "~(generateExportName!(className, sym)));
331             //Func signature
332             //Check if it is a non value type
333             mixin(generateExportFunc!(className, sym));
334         }
335     }
336 }
337 
338 
339 /**
340 *   Iterates through a module and generates `export` function declaration for each
341 *   @ExportD function found on it.
342 *   If the class itself is @ExportD, it will create a method new(ClassName) to be exported too
343 */
344 mixin template ExportDFunctions(alias mod)
345 {
346 	import std.traits:getSymbolsByUDA, ParameterIdentifierTuple;
347     pragma(msg, "Exporting ", mod.stringof);
348 	static foreach(mem; __traits(allMembers, mod))
349 	{
350         //Currently only supported on classes and structs
351 		static if( (is(__traits(getMember, mod, member) == class) || is(__traits(getMember, mod, member) == struct) ))
352 		{
353             mixin ExportDFunctionsImpl!(mem, __traits(getMember, mod, member));
354 		}
355 	}
356 }
357 
358 
359 /**
360 *   Intermediary step for getting an alias to the Class type
361 *   The difference with HipExportDFunctionsImpl is that it does not generate
362 *   Static method output when not in script version.
363 */
364 mixin template HipExportDFunctionsImpl(string className, Class)
365 {
366     //If the class has ExportD, it will export a function called new(ClassName)
367     //It can't contain more than one constructor.
368     static if(hasUDA!(Class, ExportD))
369     {
370         static assert(
371             __traits(getOverloads, Class, "__ctor").length == 1, 
372             "Can't export class with more than one constructor ("~className~")"
373         );
374         mixin(
375             generateExportConstructor!(Class, className)
376         );
377         pragma(msg, "Exported Class "~className);
378     }
379     version(Load_DScript)
380     {
381         //Get all static methods that has ExportD
382         static foreach(sym; getSymbolsByUDA!(Class, ExportD))
383         {
384             static if(!is(sym == Class))
385             {
386                 //Assert that the symbol to generate does not exists yet
387                 static assert(__traits(compiles, mixin(generateExportName!(className, sym))),
388                 "ExportD '" ~ generateExportName!(className, sym) ~
389                 "' is not unique, use ExportD(\"SomeName\") for overloading with a suffix");
390 
391                 pragma(msg, "Exported "~(generateExportName!(className, sym)));
392                 //Func signature
393                 //Check if it is a non value type
394                 mixin(generateExportFunc!(className, sym));
395             }
396         }
397     }
398 }
399 
400 /**
401 *   Iterates through a module and generates `export` function declaration for each
402 *   @ExportD function found on it.
403 *   If the class itself is @ExportD, it will create a method new(ClassName) to be exported too
404 *   *   The difference with HipExportDFunctions is that it does not generate
405 *   *   Static method output when not in script version.
406 */
407 mixin template HipExportDFunctions(alias mod)
408 {
409 	import std.traits:getSymbolsByUDA;
410     pragma(msg, "Exporting ", mod.stringof);
411 	static foreach(mem; __traits(allMembers, mod))
412 	{
413         //Currently only supported on classes and structs
414 		static if( (is(__traits(getMember, mod, mem) == class) || is(__traits(getMember, mod, mem) == struct) ))
415 		{
416             mixin HipExportDFunctionsImpl!(mem, __traits(getMember, mod, mem));
417 		}
418 	}
419 }
420 
421 
422 string attributes(alias member)() 
423 {
424 	string ret;
425 	foreach(attr; __traits(getFunctionAttributes, member))
426 		ret~= attr ~ " ";
427 	return ret;
428 }
429 
430 
431 
432 
433 template hasOverload(T,string member, OverloadType)
434 {
435     bool impl()
436     {
437         bool ret = false;
438         static foreach(ov; __traits(getVirtualMethods, T, member))
439             static if(is(typeof(ov) == OverloadType))
440                 ret = true;
441         return ret;
442     }
443 
444     enum hasOverload = impl;
445 }
446 
447 
448 bool isMethodImplemented(T, string member, FuncType)()
449 {
450     bool ret;
451     static foreach(overload; __traits(getVirtualMethods, T, member))
452         if(is(typeof(overload) == FuncType) && !__traits(isAbstractFunction, overload))
453             ret = true;
454     return ret;
455 }
456 
457 
458 /** 
459  * Private to forward interface
460  */
461 string ForwardFunc(alias func, string funcName, string member)()
462 {
463     return attributes!func~ " ReturnType!(ov) " ~ funcName ~ "(getParams!(ov))"~
464          "{ return " ~ member ~ "." ~funcName ~ "(__traits(parameters));}";
465 }
466 
467 /**
468 *   This function receives a string containing the member name which implements the interface I.   
469 *   
470 *   So, whenever something calls the interface.memberFunction, it will forward the call to that member by doing
471 *   `void memberFunction(){member.memberFunction();}`, if the function is already defined, it will be ignored.
472 *
473 *   [Dev: Futurely, it should be changed to use `alias member` instead of getting its string.]
474 */
475 string ForwardInterface(string member, I)() if(is(I == interface))
476 {
477     import hip.util.string:replaceAll;
478 
479     return q{
480         import std.traits:ReturnType;
481         import hip.util.reflection:isMethodImplemented, ForwardFunc, getParams;
482 
483         static assert(is(typeof($member) : $I),
484             "For forwarding the interface, the member $member should be or implement $I"
485         );
486 
487         static foreach(m; __traits(allMembers, $I)) 
488         static foreach(ov; __traits(getVirtualMethods, $I, m))
489         {
490             //Check for overloads here
491             static if(!isMethodImplemented!(typeof(this), m, typeof(ov)))
492                 mixin(ForwardFunc!(ov, m, "$member"));
493         }
494     }.replaceAll("$I", I.stringof).replaceAll("$member", member);
495 }
496 
497 mixin template ForwardInterface2(string member, I) if(is(I == interface))
498 {
499     import hip.util.reflection:isMethodImplemented, ForwardFunc;
500 
501     static assert(is(typeof(mixin(member)) : I),
502         "For forwarding the interface, the member "~member~" should be or implement "~I.stringof
503     );
504 
505     static foreach(m; __traits(allMembers, I)) 
506     static foreach(ov; __traits(getVirtualMethods, I, m))
507     {
508         //Check for overloads here
509         static if(!isMethodImplemented!(typeof(this), m, typeof(ov)))
510             mixin(ForwardFunc!(ov, m, member));
511     }
512 }
513 
514 mixin template MultiInherit(I) if(is(I == interface))
515 {
516     mixin("private I ",I.stringof,"_impl");
517     mixin(ForwardInterface!(I.stringof~"_impl", I));
518 }
519 
520 
521 void GenerateGetterSettersInterfaceImpl(interface_)()
522 {
523     import hip.util.array;
524     foreach(mem; __traits(allMembers, interface_))
525     {
526         alias member = __traits(getMember, interface_, mem);
527         if(__traits(isFinalFunction, member))
528             continue;
529         auto attributes = __traits(getFunctionAttributes, member);
530         if(attributes.contains("ref"))
531             continue;
532     }
533 
534 }
535 
536 /**
537 *   Generates getter and setter for given interface.
538 *   - Final methods are excluded.
539 *   - `void` return types are excluded
540 *   - `const` methods will only generate the const getter
541 */
542 mixin template GenerateGettersSettersInterface(interface_) if(is(interface_ == interface))
543 {
544     static foreach(mem; __traits(allMembers, interface_))
545     {
546 
547     }
548 }
549 
550 /**
551 * This mixin is able to generate runtime accessors. That means that by having a string, it is
552 *possible to modify 
553 */
554 mixin template GenerateRuntimeAccessors()
555 {
556     T* getProperty(T)(string prop)
557     {
558         alias T_this = typeof(this);
559 
560         switch(prop)
561         {
562             static foreach(member; __traits(allMembers, T_this))
563             {
564                 static if(is(typeof(__traits(getMember, T_this, member)) == T))
565                 {
566                     case member:
567                         return &__traits(getMember, T_this, member);
568                 }
569             }
570             default:
571                 return null;
572         }
573     }
574 
575     void setProperty(T)(string propName, T value)
576     {
577         T* prop = getProperty!T(propName);
578         if(prop !is null)
579             *prop = value;
580     }
581 }